package com.ellison.springdemo.semaphore;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;

/**
 * 限流切面处理
 *
 * @author Ellison_Pei
 * @since 1.0
 **/
@Component
@Scope
@Aspect
public class ApiRateLimitAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(ApiRateLimitAspect.class);

    //用来存放不同类名$方法名的Semaphore(key为接口名称，value为Semaphore)
    private ConcurrentHashMap<String, Semaphore> map = new ConcurrentHashMap<>();

    private Semaphore semaphore;

    @Pointcut(value = "@annotation(com.ellison.springdemo.semaphore.RateLimit)")
    public void rateLimit() {
    }

    @Around("rateLimit()")
    public Object around(ProceedingJoinPoint joinPoint) {

        Object obj = null;
        //获取拦截的方法名
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method currentMethod = methodSignature.getMethod();
        //获取注解信息
        RateLimit annotation = currentMethod.getAnnotation(RateLimit.class);
        //获取定义的每秒限流的数量
        int limitNum = annotation.limitNum();
        //methodSignature.getName();
        //注解所在方法名区分不同的限流策略
        String functionName = currentMethod.getDeclaringClass().getName() + "#" + currentMethod.getName();

        if (!map.containsKey(functionName)) {
            map.put(functionName, new Semaphore(limitNum, false));
        }
        semaphore = map.get(functionName);

        LOGGER.info("执行限流策略，执行方法：【{}】", functionName);
        LOGGER.info("限流数量：【{}】", limitNum);

        //方案1：超过阀值时，新请求被阻塞
        try {
            //无参方法tryAcquire()的作用是尝试的获得1个许可，如果获取不到则返回false，该方法通常与if语句结合使用，其具有无阻塞的特点。
            //无阻塞的特点可以使线程不至于在同步处一直持续等待的状态，如果if语句判断不成立则线程会继续走else语句，程序会继续向下运行。
            if (semaphore.tryAcquire()) {
                //执行方法
                obj = joinPoint.proceed();
            } else {
                //拒绝了请求（服务降级）
                LOGGER.info("并发数超出限流设置，走降级方法！！！【系统繁忙，稍后再试】");
                return "系统繁忙，稍后再试";
            }
        } catch (Throwable throwable) {
            LOGGER.error(throwable.getMessage(), throwable);
        } finally {
            //每个线程执行完，释放一个许可证，默认是1
            semaphore.release();
        }

        //方案2：超过阀值时，新请求被阻塞，2选1啊
        //try {
        //    //获取1个许可证，如果没有可用许可证，一直阻塞这里
        //    semaphore.acquire(1);
        //    //执行方法
        //    obj = joinPoint.proceed();
        //
        //} catch (Throwable throwable) {
        //
        //} finally {
        //    //释放一个许可证，默认也是1，会将指定的线程数释放，然后唤醒等待的线程
        //    semaphore.release();
        //}
        return obj;
    }

    private Map<String, Object> getRequestParameters(JoinPoint joinPoint) {
        List<Object> parameters = new ArrayList<>();
        Map<String, Object> primaryParmas = new TreeMap<String, Object>();

        Object[] args = joinPoint.getArgs();
        String[] names = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
        Class[] types = ((CodeSignature) joinPoint.getSignature()).getParameterTypes();
        for (int i = 0; i < args.length; i++) {
            Object arg = args[i];
            if (arg instanceof ServletRequest) {
                continue;
            }
            primaryParmas.put(names[i], arg);
        }
        return primaryParmas;
    }

    private String getIpAddr() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    private <T extends Annotation> T getAnnotation(JoinPoint joinPoint, Class<T> annotationClass) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        return method.getAnnotation(annotationClass);
    }
}
